#include "context.h"
#include "compile.h"
#include "asmfile.h"
#include "output.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>


#define NAME_MAX 128


static int varGlob_lineDecl(int addr);


/********************************* Types **************************************/

Type type_entier(int is_const) {
	Type res = {0, 0};
   if (is_const)
      res.constant_known = 1;
	return res;
}

Type type_pointeur(Type pointee, int is_const) 
{
   Type res;
   
   if (pointee.indirection == -1)
      cerror("Pointers on functions are not supported yet");
   
   if (pointee.indirection >= 30)
      cerror("indirection overdose");

   res.indirection = pointee.indirection + 1;
   
   res.constant_known = pointee.constant_known;
   if (is_const)
      res.constant_known |= (1 << res.indirection);
   
   return res;
}

Type type_fonction()
{
   // pas encore de solution pour typer convenablement les fonctions. Les 
   // ointeurs sur fonctions / tableaux de fonctions auraient besoin d'une 
   // intégration homogène.
   Type res;
   res.indirection = -1;
   return res;
}

void type_setConst(Type* t, int is_const) 
{
   if (is_const)
      t->constant_known |= (1 << t->indirection);
   else
      t->constant_known &= ~(1 << t->indirection);
}

int type_knownValue(Type t) {
   return (t.constant_known & 0x80000000);
}

void type_setKnownValue(Type* t, int k) 
{
   if (k) {
      t->constant_known |= (1 << 31);
      type_setConst(t, k);
   } else
      t->constant_known &= ~(1 << 31);
}

char* type_repr(Type t)
{
#define WRITE(msg, size) do {memcpy(res+len, msg, size); len += size;} while(0)
   
   char* res = malloc(256); // avec limite à 31 indirections, ça passe tout juste
   int len = 0;

   if (t.indirection == -1)
      WRITE("<function>", 10);
   else {
      if (t.constant_known & (1 << 0)) 
         WRITE("const ", 6);   
      WRITE("int ", 4);
      for (int i=1; i <= t.indirection; ++i) {
         WRITE("*", 1);
         if (t.constant_known & (1 << i))
            WRITE(" const ", 7);
      }
   }

#undef WRITE
   
   if (res[len - 1] == ' ')
      len--;
   res[len] = '\0';
   
   return res;
}


int type_equal(Type t1, Type t2)
{
   if (t1.indirection != t2.indirection)
      return 0;
   
   int s = 31 - t1.indirection;
   // comparer seulement les bits d'intérêt (les autres sont MàZ ou perdus lors du shift)
   return ((t1.constant_known << s) == (t2.constant_known << s));
}

int type_canAffect(Type t1, Type t2) 
{
   if (t1.indirection == -1) // can't affect to function for now
      return -1;
   
   if (t1.constant_known & (1 << t1.indirection))
      return -1; // can't affect to const
   
   t1.constant_known |= (1 << t1.indirection); // can ignore final const now 
   type_setKnownValue(&t2, 0);
   
   if (t1.indirection != t2.indirection)
      return -2;
   
   // pointers: constitude can only increase
   if (t1.indirection && (t2.constant_known & ~t1.constant_known))
      return -3;

   return 0; // OK
}

int type_canCompare(Type t1, Type t2)
{
   // pas tellement sûr du critère ici. Ignorer tous les 'consts', ou seulement
   // le premier ?
   return t1.indirection == t2.indirection;
}

/******************************* Fonctions ************************************/

ListeArguments argListe_new()
{
   ListeArguments res = {};
   return res;
}

void argListe_add(ListeArguments* l, Type t)
{
   if (l->size >= 16)
      cerror("Too many function parameters");
   
   l->types[l->size] = t;
   ++(l->size);
}

int argListe_equal(ListeArguments l1, ListeArguments l2)
{
   if (l1.size != l2.size)
      return 0;
   
   int i;
   for (i = 0; i < l1.size && type_equal(l1.types[i], l2.types[i]); ++i);
   return i == l1.size;
}


typedef struct {
   char name[NAME_MAX];
	Type typeRetour;
   ListeArguments args;
	int lineDecl;
   int iCorps; // ref. FunCorps
} Fonction;

typedef struct {
   int iFun; // ref. Fonction
	int addrDebut;
   int addrFin;
   int lineDef;
} FunCorps;

Fonction tabFonctions[32];
int sizeFonctions = 0;

FunCorps tabFunCorps[32]; // triés par ordre d'apparition des corps dans le fichier.
int sizeFunCorps = 0;

int tabFonction_add(const char* name, Type tr, ListeArguments args, int iLine)
{
   int prev;
   for (prev = 0; 
        prev < sizeFonctions && strncmp(name, tabFonctions[prev].name, NAME_MAX) != 0; 
        ++prev);
   
   if (prev != sizeFonctions) { // name already known
      Fonction* of = tabFonctions + prev;
      if (!(type_equal(tr, of->typeRetour) && argListe_equal(args, of->args))) {
         cerror("Signature of function '%s' does not match previous declaration "
                "at line %d", name, of->lineDecl);
      }
      else 
         return prev;
   }
   // else: name is new
   
   Addr var = tabSymboles_find(name, NULL);
   if (var.addr != -1) {
      int pl = varGlob_lineDecl(var.addr);
      cerror("Name '%s' is already in use (previous declaration at line %d)", name, pl);
   }
   
   if (sizeFonctions >= 32)
      cerror("Too many functions declared");
   
   Fonction* f = tabFonctions + sizeFonctions;
   strncpy(f->name, name, NAME_MAX);
   f->typeRetour = tr;
   f->args = args;
   f->iCorps = -1; // déclarée mais pas définie.
   f->lineDecl = iLine;
   
   ++sizeFonctions;
   return sizeFonctions - 1;
}

int tabFonction_get(const char* name)
{
   int id;
   for (id = 0; 
        id < sizeFonctions && strncmp(name, tabFonctions[id].name, NAME_MAX) != 0; 
        ++id);
   return (id == sizeFonctions) ? (-1) : (id);
}

void tabFonction_beginCorps(int fonction, int addr, int iLine)
{
   Fonction* f = tabFonctions + fonction;
   
   if (f->iCorps >= 0) {
      cerror("Function '%s' already has a body (previous declaration on line %d)", 
             f->name, tabFunCorps[f->iCorps].lineDef);
   }
   
   f->iCorps = sizeFunCorps;
   FunCorps* c = tabFunCorps + sizeFunCorps;
   c->iFun = fonction;
   c->lineDef = iLine;
   c->addrDebut = addr;
   c->addrFin = -1;
   
   ++sizeFunCorps;
}

void tabFonction_endCorps(int fonction, int addr)
{
   Fonction* f = tabFonctions + fonction;
   tabFunCorps[f->iCorps].addrFin = addr;
}

int tabFonction_getCorps(const char* name, int* pDebut, int* pFin)
{
   int id;
   for (id = 0; 
        id < sizeFonctions && strncmp(name, tabFonctions[id].name, NAME_MAX) != 0; 
        ++id);
   if (id == sizeFonctions || tabFonctions[id].iCorps == -1)
      return -1;
   
   FunCorps* fc = tabFunCorps + tabFonctions[id].iCorps;
   *pDebut = fc->addrDebut;
   *pFin = fc->addrFin;
   return id;
}

void tabFonction_corpsMoved(int fnct, int depl)
{
   FunCorps* fc = tabFunCorps + tabFonctions[fnct].iCorps;
   fc->addrDebut += depl;
   fc->addrFin += depl;
}

int tabFonction_findLastGap(int* pDebF, int* pFinF, int* pFinG)
{
   int aDebSuiv = output_cur();
   int iFunCorps = sizeFunCorps;
   while (iFunCorps > 0)
   {
      --iFunCorps;
      FunCorps* fc = tabFunCorps + iFunCorps;
      //printf("flg considering %d: (%d:%d)\n", iFunCorps, fc->addrDebut, fc->addrFin);
      if (fc->addrFin != aDebSuiv) { // gap
         *pDebF = fc->addrDebut;
         *pFinF = fc->addrFin;
         *pFinG = aDebSuiv;
         return fc->iFun;
      }
      // else:
      aDebSuiv = fc->addrDebut;
   }
   // no gap
   return -1;
}

/*************************** Table des symboles *******************************/

#define MAX_TAB_SYMBOLE 256

struct Variable 
{
	char name[NAME_MAX];
	Type type;
	int init;
	int lineDecl;
};
typedef struct Variable Variable;

// table de symbole globale pour des variables locales
static Variable tabSymboles[MAX_TAB_SYMBOLE]; // la machine cible a 256 octets de RAM.
// table de symbole pour des variables globales
static Variable tabVariablesGlobales[MAX_TAB_SYMBOLE];
static int sizeLocal = 0; // taille de table de symbole variables locales
static int sizeGlobal = 0; // taille de table de symbole variables globales

static int varGlob_lineDecl(int addr)
{
   return tabVariablesGlobales[addr].lineDecl;
}

/*** FrameStack ***/

#define MAX_FRAMES 32
// une pile qui stocke les tables de symboles locales
static int framebases[MAX_FRAMES]; // Base at 0 is implicit
static int cFrames = -1;

void framestack_push(int b)
{
	cFrames++;
	if (cFrames >= MAX_FRAMES)
		cerror("Maximum block depth exceeded\n");

	framebases[cFrames] = b;
}

void framestack_pop()
{
	cFrames--;
}

int framestack_top()
{
	return (cFrames == -1) ? (0) : (framebases[cFrames]);
}

/*** TabSymboles ***/

// Détermine s'il est possible d'ajouter une variable de portée spécifiée sans 
// collision de noms. Emet le warning, ou arrête la compilation, en fonction de la situation.
static void chkNameClash(const char* name, int isGlobal)
{
   Addr prev = tabSymboles_find(name, NULL);
	if (prev.addr != -1) 
   {
      int pl;
      if (prev.space == GLOBAL) 
         pl = tabVariablesGlobales[prev.addr].lineDecl;
      else
         pl = tabSymboles[prev.addr].lineDecl;
      if (prev.space == GLOBAL) {
         if (isGlobal)
            cerror("Variable name '%s' is already in use (previous declaration at line %d)", name, pl);
         else
            cwarning("Variable '%s' hides the scope of another variable "
                     "(previous declaration at line %d)", name, pl);
      }
      else { // previous variable is local
         if (prev.addr < framestack_top()) {
            cwarning("Variable '%s' hides the scope of another variable "
                     "(previous declaration at line %d)", name, pl);
         } 
         else
            cerror("Variable name '%s' is already in use (previous declaration at line %d)", name, pl);
      }
	}
   
   int fn = tabFonction_get(name);
   if (fn != -1) 
   {
      int pl = tabFonctions[fn].lineDecl;
      if (isGlobal) 
         cerror("Name '%s' is already in use (previous declaration at line %d)", name, pl);
      else
         cwarning("Variable '%s' hides the scope of another variable "
                  "(previous declaration at line %d)", name, pl);
   }
   
   // OK: on peut continuer.
}

Addr tabSymboles_push(Type t, const char* name, int isGlobal, int iLine)
{
   Addr res;
   Variable* tab = NULL;
   int* size = NULL;
   
   chkNameClash(name, isGlobal);
   
	// push une variable dans la table de symboles locale
	if(!isGlobal)
   {
		//printf("--push %s at line %d\n", name, iLine);	
		tab = tabSymboles;
      size = &sizeLocal;
      res.space = LOCAL;
	}
   else // variable globale
   {
		//printf("--glob %s\n", name);  
      tab = tabVariablesGlobales;
      size = &sizeGlobal;
      res.space = GLOBAL;
   }
   
   Variable* r = &(tab[*size]);
	strncpy(r->name, name, NAME_MAX);
	r->init = 0;
	r->lineDecl = iLine;
	r->type = t;

	++(*size);
	if ((sizeLocal + sizeGlobal) >= MAX_TAB_SYMBOLE)
	   cerror("Target machine will run out of memory");
   res.addr = (*size) -1;
   return res;	
}

// inseère un trou dans la table des variables globales (compare pushTmp())
static void pushNamelessGlob()
{
	Variable* r = tabVariablesGlobales + sizeGlobal;
	r->name[0] = '\0';
	r->init = 1; //unchecked mode

	//printf("-pushTmp %d\n", size);
   ++sizeGlobal;
   if ((sizeLocal + sizeGlobal) >= MAX_TAB_SYMBOLE)
      cerror("Target machine will run out of memory");
}

Addr tabSymboles_pushArray(Type t, int cElt, const char* name, int isGlobal, int iLine)
{
   Addr res;
   int i;
   Variable* tab = NULL;
   int* size = NULL;
   
   chkNameClash(name, isGlobal);

	if(!isGlobal)
   {
		//printf("--push %s[%d]\n", name, cElt);
		tab = tabSymboles;
      size = &sizeLocal;
      res.space = LOCAL;
	}
   else // variable globale
   {
		//printf("--glob %s[%d]\n", name, cElt);  
      tab = tabVariablesGlobales;
      size = &sizeGlobal;
      res.space = GLOBAL;
   }

   t = type_pointeur(t, 1);
   type_setKnownValue(&t, 1);
   
	Variable* r = &(tab[*size]);
	strncpy(r->name, name, NAME_MAX);
	r->init = 1; // value of symbol is its own address
	r->lineDecl = iLine;
	r->type = t;
   
	++(*size);
   if ((sizeLocal + sizeGlobal) >= MAX_TAB_SYMBOLE)
      cerror("Target machine will run out of memory");

   // OK puisque les temporaires 'perdus' sont récupérés en sortie de bloc.
   for (i=1; i < cElt; i++) {
      if (isGlobal)
         pushNamelessGlob();
      else
         tabSymboles_pushTmp(); 
   }
   
   res.addr = *size - cElt;
   return res;
}

int tabSymboles_nbOctetsVariablesGlobales()
{
   return sizeGlobal;
}

Addr tabSymboles_find(const char* name, Type* pType)
{
   Addr res;
   res.space = LOCAL;
   //printf("get '%s', size is %d\n", name, sizeLocal);
   
	// rech. linéaire en partant de la fin
	int i = sizeLocal - 1;
	while (i >= 0) 
	{
		Variable* r = tabSymboles + i;
      //printf("considering '%s' \n", r->name);
		if (strcmp(name, r->name) == 0) {
			if (pType)
				*pType = r->type;
			res.addr = i;
         return res;
		}
		i--;
	}
   
   res.space = GLOBAL;
   i = sizeGlobal - 1;
	while (i >= 0) 
	{
		Variable* r = tabVariablesGlobales + i;
		if (strcmp(name, r->name) == 0) {
			if (pType)
				*pType = r->type;
			res.addr = i;
         return res;
		}
		i--;
	}

	// pas trouvé
	res.addr = -1;
   return res;
}



const char* tabSymboles_nameByAddr(Addr addr)
{
   Variable* tab = (addr.space == GLOBAL) ? (tabVariablesGlobales) : (tabSymboles);
   return tab[addr.addr].name;
}

void tabSymboles_flagInit(Addr addr)
{
   Variable* tab = (addr.space == GLOBAL) ? (tabVariablesGlobales) : (tabSymboles);
	tab[addr.addr].init = 1;
}

int tabSymboles_isInit(Addr addr)
{
   Variable* tab = (addr.space == GLOBAL) ? (tabVariablesGlobales) : (tabSymboles);
	return tab[addr.addr].init;
}


Addr tabSymboles_pushTmp()
{
   Addr res;
	Variable* r = tabSymboles + sizeLocal;
	r->name[0] = '\0';
	r->init = 1; //unchecked mode


	//printf("-pushTmp %d\n", sizeLocal);
   ++sizeLocal;
   if ((sizeLocal + sizeGlobal) >= MAX_TAB_SYMBOLE)
      cerror("Target machine will run out of memory");

   res.addr = sizeLocal - 1;
   res.space = LOCAL;
   return res;
}

void tabSymboles_popTmp()
{
	--sizeLocal;
	//printf("-popTmp: %d\n", sizeLocal);
}

int tabSymboles_isTmp(Addr addr) {
   return addr.space == LOCAL && tabSymboles[addr.addr].name[0] == '\0';
}

void tabSymboles_protectTmp(Addr addr, int protect)
{
   assert(addr.space == LOCAL);
   int c = tabSymboles[addr.addr].name[0];
   assert(c == '\0' || c == ' ');
   
   if (protect) {  
      // don't judge me
      tabSymboles[addr.addr].name[0] = ' ';
      tabSymboles[addr.addr].name[1] = '\0';
   }
   else {
      tabSymboles[addr.addr].name[0] = '\0';
   }
}

void tabSymboles_pushFrame()
{
	//printf("--pushFrame\n");

	framestack_push(sizeLocal);
}

void tabSymboles_popFrame()
{
	//printf("--popFrame\n");

	int base = framestack_top();

	while (sizeLocal > base) {
		--sizeLocal;
		Variable* r = tabSymboles + sizeLocal;
		//printf("--pop %s\n", r->name);
		r->name[0] = '\0';
	}
   
   framestack_pop();
}

/***************************** Contrôle de flux *******************************/

// les gens normaux redécoupent leurs fonctions avant d'imbriquer autant de IFs.
#define MAX_FLOWCTRL_DEPTH 64
static ControlFrame fctrlStack[MAX_FLOWCTRL_DEPTH];
static int cFCtrlFrames = -1;

ControlFrame* flowCtrl_top = NULL;

void flowCtrl_push(CtrlType t)
{
	cFCtrlFrames++;
	assert(cFCtrlFrames <= MAX_FLOWCTRL_DEPTH);
	flowCtrl_top = fctrlStack + cFCtrlFrames;
   
   flowCtrl_top->type = t;
   flowCtrl_top->cBreaks = 0;
}

ControlFrame* flowCtrl_innerLoop()
{
   int h;
   for (h = cFCtrlFrames; h >= 0 && fctrlStack[h].type != CTRL_BOUCLE; --h);
   return (h < 0) ? (NULL) : (fctrlStack + h);
}

void flowCtrl_addBreak(int addr)
{
   ControlFrame* f = flowCtrl_innerLoop();
   if (!f)
      cerror("'break' has no power here");
   
   if (f->cBreaks >= MAX_BREAKS)
      cerror("too many 'break's in this loop");
   
   f->breaks[f->cBreaks] = addr;
   f->cBreaks++;
}

void flowCtrl_pop(int addr)
{
	assert(cFCtrlFrames >= 0);
   
   //printf("-- flowCtrl pop\n");
   
   // write pending 'break' jumps
   ControlFrame* f = flowCtrl_top;
   for (int i=0; i < f->cBreaks; ++i) {
      Addr a = {addr - f->breaks[i], LOCAL};
      out_at(f->breaks[i], JMP, a);
   }
   
   // stack pop
	cFCtrlFrames--;
	flowCtrl_top = (cFCtrlFrames < 0) ? (NULL) : (fctrlStack + cFCtrlFrames);
}
